package com.nebula.core.dsl.operation;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.nebula.core.client.NebulaSession;
import com.nebula.core.constant.Constant;
import com.nebula.core.dsl.builder.*;
import com.nebula.core.entity.vo.Edge;
import com.nebula.core.entity.vo.FieldDesc;
import com.nebula.core.entity.vo.Vertex;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.core.util.ReflectionUtil;
import org.neo4j.cypherdsl.core.Cypher;
import org.neo4j.cypherdsl.core.Node;
import org.neo4j.cypherdsl.core.Relationship;
import org.neo4j.cypherdsl.core.Statement;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * @author zhangtao
 */
@Slf4j
public class NgqlBuilder {

    private static final String EDGE_FORMAT = "'%s' -> '%s'";

    private NgqlBuilder() {
    }

    public static NgqlBuilder create() {
        return new NgqlBuilder();
    }

    /**
     * 插入点
     *
     * @param tagName
     * @param vertex
     * @return
     */
    public static String insertOneVertex(String tagName, Vertex vertex) {
        List<Vertex> list = new ArrayList<>();
        list.add(vertex);
        return VertexOperation.insertVertex(tagName, list);
    }

    /**
     * 插入单个顶点
     *
     * @param entity
     * @return
     */
    public static String insertOneVertex(Object entity) {
        List<Vertex> list = new ArrayList<>();
        Vertex vertex = NgqlBuilder.entityToVertex(entity);
        list.add(vertex);
        return VertexOperation.insertVertex(vertex.getTagName(), list);
    }

    /**
     * 批量插入点
     *
     * @param vertexList
     * @return
     */
    public static String insertManyVertex(List<Object> vertexList) {
        StringJoiner ngql = new StringJoiner(";");
        for (Object entity : vertexList) {
            ngql.add(NgqlBuilder.insertOneVertex(entity));
        }
        return ngql.toString();
    }


    /**
     * 批量插入点
     *
     * @param tagName
     * @param vertexList
     * @return
     */
    public static String insertManyVertex(String tagName, List<Vertex> vertexList) {
        return VertexOperation.insertVertex(tagName, vertexList);
    }

    /**
     * 删除点
     *
     * @param vids
     * @return
     */
    public static String deleteVertex(String... vids) {
        return VertexOperation.deleteVertex(vids);
    }

    /**
     * 更新点
     *
     * @param tagName
     * @param vertex
     * @return
     */
    public static String updateVertex(String tagName, Vertex vertex) {
        return VertexOperation.updateVertex(tagName, vertex);
    }

    /**
     * 更新点
     *
     * @param entity
     * @return
     */
    public static String updateVertex(Object entity) {
        Vertex vertex = NgqlBuilder.entityToVertex(entity);
        return VertexOperation.updateVertex(vertex.getTagName(), vertex);
    }

    /**
     * 插入边
     *
     * @param edgeType
     * @param edge
     * @return
     */
    public static String insertOneEdge(String edgeType, Edge edge) {
        List<Edge> list = new ArrayList<>();
        list.add(edge);
        return EdgeOperation.insertEdge(edgeType, list);
    }

    /**
     * 插入边
     *
     * @param entity
     * @return
     */
    public static String insertOneEdge(Object entity) {
        Edge edge = NgqlBuilder.entityToEdge(entity);
        List<Edge> list = new ArrayList<>();
        list.add(edge);
        return EdgeOperation.insertEdge(edge.getEdgeType(), list);
    }

    /**
     * 批量插入边
     *
     * @param edgeType
     * @param edgeList
     * @return
     */
    public static String insertManyEdge(String edgeType, List<Edge> edgeList) {
        return EdgeOperation.insertEdge(edgeType, edgeList);
    }

    /**
     * 批量插入边
     *
     * @param edgeList
     * @return
     */
    public static String insertManyEdge(List<Object> edgeList) {
        StringJoiner ngql = new StringJoiner(";");
        for (Object entity : edgeList) {
            ngql.add(NgqlBuilder.insertOneEdge(entity));
        }
        return ngql.toString();
    }

    /**
     * 删除边
     *
     * @param edgeType
     * @param edge
     * @return
     */
    public static String deleteEdge(String edgeType, Edge... edge) {
        StringBuilder ngql = new StringBuilder("DELETE EDGE ");
        ngql.append(edgeType);
        ngql.append(" ");
        StringJoiner vidsJoin = new StringJoiner(",");
        Arrays.stream(edge).forEach(e -> {
            vidsJoin.add(String.format(EDGE_FORMAT, e.getSrcId(), e.getDstId()));
        });
        ngql.append(vidsJoin);
        return ngql.toString();
    }

    /**
     * 删除边
     *
     * @param edgeList
     * @return
     */
    public static String deleteEdge(Object... edgeList) {
        String edgeType = "";
        StringJoiner vidsJoin = new StringJoiner(",");
        for (Object entity : edgeList) {
            Edge edge = NgqlBuilder.entityToEdge(entity);
            edgeType = edge.getEdgeType();
            vidsJoin.add(String.format(EDGE_FORMAT, edge.getSrcId(), edge.getDstId()));
        }
        StringBuilder ngql = new StringBuilder("DELETE EDGE ");
        ngql.append(edgeType);
        ngql.append(" ");
        ngql.append(vidsJoin);
        return ngql.toString();
    }

    /**
     * 插入边
     *
     * @param edgeType
     * @param edge
     * @return
     */
    public static String updateEdge(String edgeType, Edge edge) {
        return EdgeOperation.updateEdge(edgeType, edge);
    }

    /**
     * 更新边
     *
     * @param entity
     * @return
     */
    public static String updateEdge(Object entity) {
        Edge edge = NgqlBuilder.entityToEdge(entity);
        return EdgeOperation.updateEdge(edge.getEdgeType(), edge);
    }

    /**
     * go 查询
     *
     * @return
     */
    public static GoStatementBuilder.GoStart go() {
        return GoOperation.create();
    }

    /**
     * go 查询
     *
     * @param nebulaSession
     * @return
     */
    public static GoStatementBuilder.GoStart go(NebulaSession nebulaSession) {
        return GoOperation.create(nebulaSession);
    }

    /**
     * fetch 查询
     *
     * @return
     */
    public static FetchStatementBuilder.FetchType fetch() {
        return FetchOperation.create();
    }

    /**
     * fetch 查询
     *
     * @param nebulaSession
     * @return
     */
    public static FetchStatementBuilder.FetchType fetch(NebulaSession nebulaSession) {
        return FetchOperation.create(nebulaSession);
    }

    /**
     * getSubgraph 查询
     *
     * @return
     */
    public static GetSubgraphStatementBuilder.GetSubgraphStart getSubgraph() {
        return GetSubgraphOperation.create();
    }

    /**
     * getSubgraph 查询
     *
     * @param nebulaSession
     * @return
     */
    public static GetSubgraphStatementBuilder.GetSubgraphStart getSubgraph(NebulaSession nebulaSession) {
        return GetSubgraphOperation.create(nebulaSession);
    }

    /**
     * getSubgraph 查询
     *
     * @return
     */
    public static FindPathStatementBuilder.FindPathType findPath() {
        return FindPathOperation.create();
    }

    /**
     * getSubgraph 查询
     *
     * @param nebulaSession
     * @return
     */
    public static FindPathStatementBuilder.FindPathType findPath(NebulaSession nebulaSession) {
        return FindPathOperation.create(nebulaSession);
    }

    /**
     * lookup 查询
     *
     * @return
     */
    public static LookUpStatementBuilder.LookUpStart lookup() {
        return LookUpOperation.create();
    }

    /**
     * lookup 查询
     *
     * @param nebulaSession
     * @return
     */
    public static LookUpStatementBuilder.LookUpStart lookup(NebulaSession nebulaSession) {
        return LookUpOperation.create(nebulaSession);
    }

    /**
     * 创建Tag
     *
     * @return
     */
    public static String createTag(String tagName, List<FieldDesc> fieldDescList) {
        return SchemaOperation.createTag(tagName, fieldDescList);
    }

    /**
     * 删除Tag
     *
     * @param tagName
     * @return
     */
    public static String dropTag(String tagName) {
        return SchemaOperation.dropTag(tagName);
    }

    /**
     * 更新Tag
     *
     * @param tagName
     * @return
     */
    public static String alterTag(String tagName, List<FieldDesc> fieldDescList) {
        return SchemaOperation.alterTag(tagName, fieldDescList);
    }

    /**
     * 查询Tag的详细信息
     *
     * @return
     */
    public static String describeTag(String tag) {
        return "DESCRIBE TAG " + tag;
    }

    /**
     * 查询Tag的创建信息
     *
     * @return
     */
    public static String showCreateTag(String tag) {
        return "SHOW CREATE TAG " + tag;
    }


    /**
     * 创建Edge
     *
     * @return
     */
    public static String createEdge(String edgeType, List<FieldDesc> fieldDescList) {
        return SchemaOperation.createEdge(edgeType, fieldDescList);
    }

    /**
     * 删除Edge
     *
     * @param edgeType
     * @return
     */
    public static String dropEdge(String edgeType) {
        return SchemaOperation.dropEdge(edgeType);
    }

    /**
     * 更新Edge
     *
     * @param edgeType
     * @return
     */
    public static String alterEdge(String edgeType, List<FieldDesc> fieldDescList) {
        return SchemaOperation.alterEdge(edgeType, fieldDescList);
    }

    /**
     * 查询Edge的详细信息
     *
     * @return
     */
    public static String describeEdge(String edge) {
        return "DESCRIBE EDGE " + edge;
    }

    /**
     * 查询Edge的创建信息
     *
     * @return
     */
    public static String showCreateEdge(String edge) {
        return "SHOW CREATE EDGE " + edge;
    }

    /**
     * 查询Tag的列表
     *
     * @return
     */
    public static String showTags() {
        return "SHOW TAGS;";
    }

    /**
     * 查询Edge的列表
     *
     * @return
     */
    public static String showEdges() {
        return "SHOW EDGES;";
    }

    /**
     * 根据vid查询节点
     *
     * @param vid
     * @return
     */
    public static Statement findNodeByVid(String vid) {
        Node node = Cypher.anyNode("v");
        Statement statement = Cypher.match(node)
                .where(node.internalId().eq(Cypher.literalOf(vid)))
                .returning(node).build();
        return statement;
    }

    /**
     * 根据vid查询边
     *
     * @param srcId
     * @param dstId
     * @return
     */
    public static Statement findEdgeByVid(String srcId, String dstId) {
        Node s = Cypher.anyNode("s");
        Node d = Cypher.anyNode("d");
        Relationship r = s.relationshipBetween(d).named("r");
        Statement statement = Cypher.match(r)
                .where(s.internalId().eq(Cypher.literalOf(srcId)))
                .and(d.internalId().eq(Cypher.literalOf(dstId)))
                .returning(r).build();
        return statement;
    }

    public static Edge entityToEdge(Object entity) {
        Edge edge = new Edge();
        Map<String, Object> properties = new HashMap<>();

        String edgeType = StrUtil.toUnderlineCase(entity.getClass().getSimpleName());

        TableName tableName = AnnotationUtil.getAnnotation(entity.getClass(), TableName.class);
        if (tableName != null && StrUtil.isNotBlank(tableName.value())) {
            edgeType = tableName.value();
        }
        for (Field f : getAllFields(entity.getClass())) {
            if (Modifier.isFinal(f.getModifiers())) {
                continue;
            }
            String fieldName = StrUtil.toUnderlineCase(f.getName());
            TableField tableField = AnnotationUtil.getAnnotation(f, TableField.class);
            if (tableField != null && StrUtil.isNotBlank(tableField.value())) {
                fieldName = tableField.value();
            }
            Object value = ReflectionUtil.getFieldValue(f, entity);
            if (value != null && Constant.SRC_ID.equals(f.getName())) {
                edge.setSrcId(value.toString());
            } else if (value != null && Constant.DST_ID.equals(f.getName())) {
                edge.setDstId(value.toString());
            } else if (value != null) {
                properties.put(fieldName, value);
            }
        }
        edge.setEdgeType(edgeType);
        edge.setProperties(properties);
        return edge;
    }


    public static Vertex entityToVertex(Object entity) {
        Vertex vertex = new Vertex();
        Map<String, Object> properties = new HashMap<>();

        String tagName = StrUtil.toUnderlineCase(entity.getClass().getSimpleName());

        TableName tableName = AnnotationUtil.getAnnotation(entity.getClass(), TableName.class);
        if (tableName != null && StrUtil.isNotBlank(tableName.value())) {
            tagName = tableName.value();
        }
        for (Field f : getAllFields(entity.getClass())) {
            if (Modifier.isFinal(f.getModifiers())) {
                continue;
            }
            String fieldName = StrUtil.toUnderlineCase(f.getName());
            TableField tableField = AnnotationUtil.getAnnotation(f, TableField.class);
            if (tableField != null && StrUtil.isNotBlank(tableField.value())) {
                fieldName = tableField.value();
            }
            Object value = ReflectionUtil.getFieldValue(f, entity);
            if (value != null && "vid".equals(fieldName)) {
                vertex.setVid(value.toString());
            } else if (value != null) {
                properties.put(fieldName, value);
            }
        }
        vertex.setProperties(properties);
        vertex.setTagName(tagName);
        return vertex;
    }

    private static Field[] getAllFields(Class<?> clazz) {
        List<Field> allFields = new ArrayList<>();
        while (clazz != null && !Object.class.equals(clazz)) {
            Field[] declaredFields = clazz.getDeclaredFields();
            Collections.addAll(allFields, declaredFields);
            clazz = clazz.getSuperclass();
        }
        return allFields.toArray(new Field[0]);
    }

}
